#include "config.h"

//purpose: xcopy impemetation for VMWare VAAI, confrim to the standard SBC-4, SPC-5 and SAM-4, LTD1 only.

//file created by zsy on 2017.08.11
//don't simply modify this code except you have fully read read above t10.org standard documents.

#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/tcp.h>

#include "iscsi.h"
#include "dbg.h"
#include "lichstor.h"

#define SCSI_CM_MAX_TGT_DESCR_CNT 8
#define SCSI_CM_MAX_SEG_DESCR_CNT 8

struct scsi_cm_tgt_desc {
		uint64_t file_id;
		unsigned int read_only:1;
		uint32_t block;
		//int param_offs;
};

//parsing CSCD descriptor parameters. This is important and dengerous so we only support less than 8 devices and those devices must be idendified by IEEE names, by shengyu at fusionstack.
static int scsi_cm_parse_id_tgt_descr(struct iscsi_cmd *cmd, const uint8_t *seg, int offs, struct scsi_cm_tgt_desc *tgt_descr)
{
		int res = 32;
		//struct scst_cm_desig *des;
		int block = 0;
		int read_only = 0;

                (void) offs;
                
		if ((seg[1] & 0xC0) != 0) {
				DWARN("LU ID %x not supported", seg[1] & 0xC0);
				iscsi_cmd_set_sense(cmd, ILLEGAL_REQUEST, 0x26, 0x00);  //invalid in param.

				goto out_err;
		}

		if ((seg[1] & 0x20) != 0) {
				DWARN("NULL tgt descriptor");
				//tgt_descr->tgt_dev = NULL;

				goto out;
		}

		if ((seg[1] & 0xF) != 0) {
				DWARN("PERIPHERAL DEVICE TYPE %d not supported", seg[1] & 0xF);
				iscsi_cmd_set_sense(cmd, ILLEGAL_REQUEST, 0x26, 0x00);  //invalid in param.

				goto out_err;
		}
		
		block = ntoh24(&seg[29]);

		if(ISCSI_IEEE_VEN_ID != cpu_to_be32(*((uint32_t *)&seg[8]))){
				DWARN("Invali scsi id in extended copy, id=%u, \r\n", cpu_to_be32(*((uint32_t *)&seg[8])));
				iscsi_cmd_set_sense(cmd, ILLEGAL_REQUEST, 0x26, 0x00);  //invalid in param.

				goto out_err;
		}

		//do find target...
		memcpy(&tgt_descr->file_id, &seg[16], sizeof(uint64_t));
		tgt_descr->block = block;
		tgt_descr->read_only = read_only;
		//not need to do more in lich, as we have the fileid.
		goto out;
		
		/*
		block = get_unaligned_be24(&seg[29]);

		mutex_lock(&scst_cm_mutex);

		list_for_each_entry(des, &scst_cm_desig_list, cm_desig_list_entry) {
			TRACE_DBG("des %p (tgt_dev %p, lun %lld)", des, des->desig_tgt_dev,
				(unsigned long long)des->desig_tgt_dev->lun);
			if (seg[4] != des->desig[0])
				continue;
			if (seg[5] != des->desig[1])
				continue;
			if (seg[7] > des->desig[3])
				continue;
			if (memcmp(&des->desig[4], &seg[8], min_t(int, seg[7], des->desig[3])) == 0) {
				TRACE_DBG("Tgt_dev %p (lun %lld) found",
					des->desig_tgt_dev,
					(unsigned long long)des->desig_tgt_dev->lun);

				mutex_unlock(&scst_cm_mutex);

				if (block != des->desig_tgt_dev->dev->block_size) {
					PRINT_WARNING("Block size %d doesn't match %d", block,
						des->desig_tgt_dev->dev->block_size);
					scst_set_invalid_field_in_parm_list(cmd, offs+29, 0);
					goto out_err;
				}

				if (!scst_cm_check_access(cmd->sess->initiator_name,
						des->desig_tgt_dev->dev, &read_only))
					goto out_not_found;

				tgt_descr->tgt_dev = des->desig_tgt_dev;
				tgt_descr->read_only = read_only;
				TRACE_DBG("Found des %p (tgt_dev %p, read_only %d)",
					des, tgt_descr->tgt_dev, tgt_descr->read_only);
				goto out;
			}
		}

		mutex_unlock(&scst_cm_mutex);

		TRACE(TRACE_MINOR|TRACE_SCSI, "Target descriptor designator not found "
			"(initiator %s, offs %d)", cmd->sess->initiator_name, offs);
		TRACE_BUFF_FLAG(TRACE_MINOR|TRACE_SCSI, "Designator", seg, 32);
*/

//out_not_found:
//		iscsi_cmd_set_sense(cmd, ILLEGAL_REQUEST, 0x26, 0x00);  //invalid in param.

out_err:
		res = -1;

out:
		
		return res;
}

static int scsi_cm_parse_and_do_b2b(struct iscsi_cmd *cmd, struct scsi_cm_tgt_desc *tgt_desc, int tgt_cnt, const uint8_t *seg, int seg_num)
{
		int src_des_idx, tgt_des_idx;
		int res = 28, len, blocks, dc, rc;
		struct scsi_cm_tgt_desc *src_des, *tgt_des;

                (void) seg_num;
                
		len = cpu_to_be16(*((uint16_t *)&seg[2]));

		if (len != 0x18) {
				DWARN("Incorrect B2B segment descriptor len %d\r\n", len);
				//scst_cm_set_seg_err_sense(ec_cmd, 0, 0, seg_num, 2);
				goto out_err;
		}

		src_des_idx = cpu_to_be16(*((uint32_t *)&seg[4]));
		if (src_des_idx >= tgt_cnt) 
		{
				DWARN("Invalid src descriptor index %d\r\n", src_des_idx);
				//scst_cm_set_seg_err_sense(ec_cmd, 0, 0, seg_num, 4);
				goto out_err;
		}

		src_des = &tgt_desc[src_des_idx];
		if (src_des->file_id == 0) {
				DWARN("Segment with NULL src tgt device\r\n");
				/* COPY TARGET DEVICE NOT REACHABLE */
				//scst_cm_set_seg_err_sense(ec_cmd, 0xD, 2, seg_num, 4);
				goto out_err;
		}

		tgt_des_idx = cpu_to_be16(*((uint32_t *)&seg[6]));
		if (tgt_des_idx >= tgt_cnt) {
				DWARN("Invalid tgt descriptor index %d", tgt_des_idx);
				//scst_cm_set_seg_err_sense(ec_cmd, 0, 0, seg_num, 6);
				goto out_err;
		}

		tgt_des = &tgt_desc[tgt_des_idx];
		if (tgt_des->file_id == 0) {
				DWARN("Segment with NULL tgt device");
				/* COPY TARGET DEVICE NOT REACHABLE */
				//scst_cm_set_seg_err_sense(ec_cmd, 0xD, 2, seg_num, 6);
				goto out_err;
		}
		if (tgt_des->read_only) {
				//PRINT_WARNING("Target descriptor refers to read-only device");
				//scst_cm_set_seg_err_sense(ec_cmd, 0, 0, seg_num, 6);
				goto out_err;
		}

		dc = (seg[1] >> 1) & 1;
		blocks = cpu_to_be16(*((uint16_t *)&seg[10]));
		if (dc)
				len = blocks * (1 << sanconf.lun_blk_shift);//!!!!!!//<< tgt_des->tgt_dev->dev->block_shift;
		else
				len = blocks * (1 << sanconf.lun_blk_shift);//!!!!<< src_des->tgt_dev->dev->block_shift;

		/*f (unlikely((len & (src_des->tgt_dev->dev->block_size-1)) != 0) ||
			unlikely((len & (tgt_des->tgt_dev->dev->block_size-1)) != 0)) {
			PRINT_WARNING("Data len %d is not even for block size (src block "
				"size %d, dst block size %d)", len,
				src_des->tgt_dev->dev->block_size,
				tgt_des->tgt_dev->dev->block_size);
			scst_cm_set_seg_err_sense(ec_cmd, 0, 0, seg_num, 10);
			goto out_err;
		}*/

		uint64_t src_lba = cpu_to_be64(*((uint64_t *)&seg[12])) * (1 << sanconf.lun_blk_shift);
		uint64_t dst_lba = cpu_to_be64(*((uint64_t *)&seg[20])) * (1 << sanconf.lun_blk_shift);

		fileid_t src_id, dst_id;

		src_id.id = src_des->file_id;
		src_id.idx = 0;
		src_id.type = __VOLUME_CHUNK__;

		dst_id.id = tgt_des->file_id;
		dst_id.idx = 0;
		dst_id.type = __VOLUME_CHUNK__;

		DINFO("xcopy src_lba=%ju, dst_lba=%ju, len=%d\r\n", src_lba, dst_lba, len);

		DINFO("xcopy src_id=%ju, dst_id=%ju\r\n", src_des->file_id, tgt_des->file_id);
		
		buffer_t buffer;
		mbuffer_init(&buffer, 0);

		rc = stor_read_remote(&src_id, &buffer, len, src_lba);
		if(rc){
				mbuffer_free(&buffer);
				goto out_err;
		}

		rc = stor_write_remote(&dst_id, &buffer, len, dst_lba);
		if(rc){
				mbuffer_free(&buffer);
				goto out_err;
		}
		
		mbuffer_free(&buffer);

		DINFO("xcopy successfully.\r\n");

		goto out;


		{//copying
				struct iscsi_cmd cpy_cmd = *cmd;

				//int left = len;
				//while(left > 0) 
				{
					cpy_cmd.tio = tio_alloc(cpy_cmd.conn, 0);
					tio_set_diskseek(cpy_cmd.tio, src_lba, len);

					int err = tio_read(&cpy_cmd);
					if(err){
							tio_free(cpy_cmd.conn, cpy_cmd.tio);
							goto out_err;
					}
				DINFO("tio_read= %d\r\n", err);

				tio_set_diskseek(cpy_cmd.tio, dst_lba, len);

				err = tio_write(&cpy_cmd);

				tio_free(cpy_cmd.conn, cpy_cmd.tio);

				if(err){
						goto out_err;
				}

				DINFO("tio_write= %d\r\n", err);
				}
		}

out:
		//TRACE_EXIT_RES(res);
		return res;

out_err:
		res = -1;
		goto out;
}

int scsi_cm_parse_descriptors(struct iscsi_cmd *cmd)
{
        int res = 0, rc;
        int list_id, list_id_usage, len, tgt_len, seg_len;
        uint8_t *buf = (uint8_t *)malloc(cmd->pdu.datasize);
        uint32_t length = cmd->pdu.datasize;
        int tgt_cnt, seg_cnt, i, offs, t;
        struct scsi_cm_tgt_desc  *tgt_desc;
        void *p;

        mbuffer_get(&cmd->tio->buffer, buf, cmd->pdu.datasize);
 
        list_id = buf[0];
	    list_id_usage = (buf[1] & 0x18) >> 3;

        DBUG("list_id=%d, list_id_usage=%d\r\n", list_id, list_id_usage);

        switch (list_id_usage)
        {
		        case 0:
		        case 2:
						DBUG("%d add to list\r\n", list_id_usage);
						//plist_id = scst_cm_add_list_id(ec_cmd, list_id);
						//if (plist_id == NULL)
						//        goto out_abn_put;
				break;
				case 3:
						if (list_id != 0) 
						{
								DWARN("Invalid list ID %d with list ID usage %d", list_id, list_id_usage);
								iscsi_cmd_set_sense(cmd, ILLEGAL_REQUEST, 0x26, 0x00);  //invalid in param.
								
								goto out_abn_put;
						}
				break;
				default:
						DWARN("Invalid list ID usage %d, rejecting", list_id_usage);
						iscsi_cmd_set_sense(cmd, ILLEGAL_REQUEST, 0x26, 0x00);  //invalid in param.
						
						goto out_abn_put;
	    }

        len = cpu_to_be32(*((uint32_t *)&buf[12]));
	    if (len != 0) 
        {
				DWARN("Inline data not supported (len %d)", len);
				iscsi_cmd_set_sense(cmd, VOLUME_OVERFLOW, 0x26, 0x00); 

				goto out_del_abn_put;
	    }

        tgt_len = cpu_to_be16(*((uint16_t *)&buf[2]));
	    seg_len = cpu_to_be32(*((uint32_t *)&buf[8]));

        if (tgt_len == 0) 
        {
				if (seg_len == 0)
						goto out_del_put;
				else 
				{
						DWARN("Zero target descriptors with non-zero "
							"segments len (%d)\r\n", seg_len);
					
						iscsi_cmd_set_sense(cmd, ILLEGAL_REQUEST, 0x26, 0x00); 
						
						goto out_del_abn_put;
				}
	    }

        DBUG("tgt_len=%d, seg_len=%d\r\n", tgt_len, seg_len);

        if ((tgt_len + seg_len + 16) > length) 
        {
				DWARN("Parameters truncation\r\n");
				iscsi_cmd_set_sense(cmd, ILLEGAL_REQUEST, 0x26, 0x00); 
				
				goto out_del_abn_put;
	    }

        if ((tgt_len + seg_len + 16) != length)
        {
				DWARN("Unexpected inline data\r\n");
				
				iscsi_cmd_set_sense(cmd, VOLUME_OVERFLOW, 0x26, 0x00); 

				goto out_del_abn_put;
	    }

	    if ((tgt_len % 32) != 0) 
        {
				DWARN("Invalid tgt len %d\r\n", tgt_len);
				iscsi_cmd_set_sense(cmd, ILLEGAL_REQUEST, 0x26, 0x00); 
				
				goto out_del_abn_put;
	    }

	    tgt_cnt = tgt_len/32;
	    if (tgt_cnt > SCSI_CM_MAX_TGT_DESCR_CNT) 
        {
				DWARN("Too many target descriptors %d\r\n", tgt_cnt);
				//scsi_set_cmd_error(ec_cmd,
				//    SCSI_LOAD_SENSE(scst_sense_too_many_target_descriptors));
				iscsi_cmd_set_sense(cmd, ILLEGAL_REQUEST, 0x26, 0x00); 

				goto out_del_abn_put;
	    }

	    DBUG("tgt_cnt %d\r\n", tgt_cnt);

	    tgt_desc = malloc(tgt_cnt * sizeof(*tgt_desc));
	    if (tgt_desc == NULL) 
        {
				DFATAL("out of memory\r\n");
				
				goto out_del_abn_put;
		}
		
		memset(tgt_desc, 0, tgt_cnt * sizeof(*tgt_desc));

        offs = 16;
        // TODO rc init
        rc = 0;
        for (i = 0; i < tgt_cnt; i++)
        {
				DBUG("offs %d\r\n", offs);
				switch (buf[offs])
				{
						case 0xE4: /* identification descriptor target descriptor format */
								rc = scsi_cm_parse_id_tgt_descr(cmd, &buf[offs], offs, &tgt_desc[i]);
								if (rc <= 0)
									goto out_free_tgt_descr;
						break;

						default:
								DWARN("Not supported target descriptor %x\r\n", buf[offs]);
								//scst_set_cmd_error(ec_cmd, SCST_LOAD_SENSE(scst_sense_unsupported_tgt_descr_type));
								goto out_free_tgt_descr;
					}

					//tgt_descrs[i].param_offs = offs;
					offs += rc;
	    }

	    //WARN_ON(offs != tgt_len + 16);

	    t = offs;
	    seg_cnt = 0;
	    while (offs < length) 
        {
				if (seg_cnt >= SCSI_CM_MAX_SEG_DESCR_CNT)
				{
						DWARN("Too many segment descriptors\r\n");
						//scst_set_cmd_error(ec_cmd, SCST_LOAD_SENSE(scst_sense_too_many_segment_descriptors));
						goto out_free_tgt_descr;
				}
				switch (buf[offs])
				{
						case 2: /* block device to block device segment descriptor */
								offs += 28;
						break;
						default:
								DWARN("Not supported segment descriptor %x\r\n", buf[offs]);
								//scst_set_cmd_error(ec_cmd,
								//    SCST_LOAD_SENSE(scst_sense_unsupported_seg_descr_type));
								goto out_free_tgt_descr;
				}

				seg_cnt++;
	    }

	    offs = t;

	    DBUG("seg_cnt %d\r\n", seg_cnt);

	    p = NULL;//malloc(sizeof(*p) + seg_cnt * sizeof(struct scst_ext_copy_seg_descr));
	    if (p == NULL)
        {
		    //TRACE(TRACE_OUT_OF_MEM, "Unable to allocate Extended Copy "
			///    "descriptors (seg_cnt %d)", seg_cnt);
		    //scst_set_busy(ec_cmd);
		    //goto out_free_tgt_descr;
	    }

	    /*p->cm_list_id = plist_id;
	    plist_id = NULL;
	    INIT_LIST_HEAD(&p->cm_sorted_devs_list);
	    INIT_LIST_HEAD(&p->cm_internal_cmd_list);
	    p->cm_error = SCST_CM_ERROR_NONE;
	    mutex_init(&p->cm_mutex);

	    ec_cmd->cmd_data_descriptors = p;
	    ec_cmd->cmd_data_descriptors_cnt = seg_cnt;

	    res = scst_cm_add_to_descr_list(ec_cmd, ec_cmd->tgt_dev);
	    if (res != 0)
		    goto out_free_p;
*/
	    for (i = 0; i < seg_cnt; i++) 
        {
				//TRACE_DBG("offs %d", offs);
				switch (buf[offs]) 
				{
						case 2: /* block device to block device segment descriptor */
								//rc = scst_cm_parse_b2b_seg_descr(ec_cmd, &buf[offs],
								//    tgt_descrs, tgt_cnt, i);
								rc = scsi_cm_parse_and_do_b2b(cmd, tgt_desc, tgt_cnt, &buf[offs],i);
								if (rc <= 0) 
								{
										if (rc == -ENOMEM)
												goto out_free_p;
										else 
										{
												/*
												* We may need to keep list_id for a
												* while for further FAILED SEGMENT
												* DETAILS of RECEIVE COPY RESULTS
												*/
													//scst_cm_store_list_id_details(ec_cmd);
												goto out_free_tgt_descr;
										}   
								}
								//EXTRACHECKS_BUG_ON(rc != 28);
								break;
						default:
						break;
						//sBUG();
				}

				offs += rc;
	}

	free(tgt_desc);

out_del_put:
	//if (plist_id != NULL)
	//	scst_cm_del_free_list_id(plist_id, 0);

// out_put:
	//scst_put_buf_full(ec_cmd, buf);

// out:
	//TRACE_EXIT_RES(res);
	return res;

out_free_p:
	//scst_cm_free_ec_priv(ec_cmd, false);

out_free_tgt_descr:
	free(tgt_desc);

out_del_abn_put:
	//if (plist_id != NULL)
//		scst_cm_del_free_list_id(plist_id, false);

out_abn_put:
//	scst_put_buf_full(ec_cmd, buf);

// out_abn:
//	scst_set_cmd_abnormal_done_state(ec_cmd);
	res = -1;
	//goto out;      

	free(buf);
	return res;
}
